import os

from typing import get_type_hints, Union, List

from dotenv import load_dotenv


load_dotenv()


class AppConfigError(Exception):

    pass


def _parse_bool(val: Union[str, bool]) -> bool:  # pylint: disable=E1136 

    return val if type(val) == bool else val.lower() in ['true', 'yes', '1']

def _parse_array(val: Union[str, List[str]]) -> list:

    return val.split(',') if type(val) == str else val


# AppConfig class with required fields, default values, type checking, and typecasting for int and bool values

class AppConfig:

    DEBUG: bool = True
    ENV: str = 'production'

    APP_PORT: int = 8081
    APP_TOKENS: str = ''
    DB_TYPE: str = 'postgresql'
    DB_HOST: str = 'localhost'
    DB_PORT: int = 5432
    DB_USER: str = 'postgres'
    DB_PASSWORD: str = 'postgres'
    DB_NAME: int = 0

    """

    Map environment variables to class fields according to these rules:

      - Field won't be parsed unless it has a type annotation

      - Field will be skipped if not in all caps

      - Class field and environment variable name are the same

    """

    def __init__(self, env):
        for field in self.__annotations__:
            if not field.isupper():
                continue

            # Raise AppConfigError if required field not supplied
            default_value = getattr(self, field, None)
            if default_value is None and env.get(field) is None:

                raise AppConfigError('The {} field is required'.format(field))

            # Cast env var value to expected type and raise AppConfigError on failure
            try:

                var_type = get_type_hints(AppConfig)[field]

                if var_type == bool:
                    value = _parse_bool(env.get(field, default_value))
                else:
                    value = var_type(env.get(field, default_value))


                self.__setattr__(field, value)

            except ValueError:
                raise AppConfigError('Unable to cast value of "{}" to type "{}" for "{}" field'.format(
                    env[field],
                    var_type,
                    field
                )

            )


    def __repr__(self):
        return str(self.__dict__)

    def get_tokens(self) -> List[str]:
        return _parse_array(self.APP_TOKENS)

# Expose Config object for app to import

Config = AppConfig(os.environ)